<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
	<title>文档的部分更新 | Elasticsearch: 权威指南 | Elastic</title>
    <!-- Give IE8 a fighting chance -->
    <!--[if lt IE 9]>
    <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
    <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
	<link rel="stylesheet" type="text/css" href="../static/styles.css" />
</head>
<body>
<div class="main-container">
    <section id="content">
        
        <div class="content-wrapper">
            <section id="guide" lang="zh_cn">
                <div class="container">
                    <div class="row">
                        <div class="col-xs-12 col-sm-8 col-md-8 guide-section">
                            <div style="color:gray; word-break: break-all; font-size:12px;">原文地址: <a href="https://www.elastic.co/guide/cn/elasticsearch/guide/current/partial-updates.html" rel="nofollow">https://www.elastic.co/guide/cn/elasticsearch/guide/current/partial-updates.html</a>, 版权归 www.elastic.co 所有<br/>
                            英文版地址: <a href="https://www.elastic.co/guide/en/elasticsearch/guide/current/partial-updates.html" rel="nofollow">https://www.elastic.co/guide/en/elasticsearch/guide/current/partial-updates.html</a>
                            </div>
                        <!-- start body -->
                  <div class="page_header">
<b>请注意:</b><br>本书基于 Elasticsearch 2.x 版本，有些内容可能已经过时。
</div>
<div id="content">
<div class="breadcrumbs">
<span class="breadcrumb-link"><a href="index.html">Elasticsearch: 权威指南</a></span>
»
<span class="breadcrumb-link"><a href="getting-started.html">基础入门</a></span>
»
<span class="breadcrumb-link"><a href="data-in-data-out.html">数据输入和输出</a></span>
»
<span class="breadcrumb-node">文档的部分更新</span>
</div>
<div class="navheader">
<span class="prev">
<a href="optimistic-concurrency-control.html">« 乐观并发控制</a>
</span>
<span class="next">
<a href="_Retrieving_Multiple_Documents.html">取回多个文档 »</a>
</span>
</div>
<div class="section">
<div class="titlepage"><div><div>
<h2 class="title">
<a id="partial-updates"></a>文档的部分更新<a class="edit_me edit_me_private" rel="nofollow" title="Editing on GitHub is available to Elastic" href="https://github.com/elasticsearch-cn/elasticsearch-definitive-guide/edit/cn/030_Data/45_Partial_update.asciidoc">edit</a>
</h2>
</div></div></div>
<p>在 <a class="xref" href="update-doc.html" title="更新整个文档">更新整个文档</a> , 我们已经介绍过
更新一个文档的方法是检索并修改它，然后重新索引整个文档，这的确如此。然而，使用 <code class="literal">update</code> API 我们还可以部分更新文档，例如在某个请求时对计数器进行累加。</p>
<p>我们也介绍过文档是不可变的：他们不能被修改，只能被替换。 <code class="literal">update</code> API 必须遵循同样的规则。
从外部来看，我们在一个文档的某个位置进行部分更新。然而在内部， <code class="literal">update</code> API 简单使用与之前描述相同的 <em>检索-修改-重建索引</em> 的处理过程。
区别在于这个过程发生在分片内部，这样就避免了多次请求的网络开销。通过减少检索和重建索引步骤之间的时间，我们也减少了其他进程的变更带来冲突的可能性。</p>
<p><code class="literal">update</code> 请求最简单的一种形式是接收文档的一部分作为 <code class="literal">doc</code> 的参数，
它只是与现有的文档进行合并。对象被合并到一起，覆盖现有的字段，增加新的字段。
例如，我们增加字段 <code class="literal">tags</code> 和 <code class="literal">views</code> 到我们的博客文章，如下所示：</p>
<div class="pre_wrapper lang-sense">
<pre class="programlisting prettyprint lang-sense">POST /website/blog/1/_update
{
   "doc" : {
      "tags" : [ "testing" ],
      "views": 0
   }
}</pre>
</div>
<div class="sense_widget" data-snippet="snippets/030_Data/45_Partial_update.json"></div>
<p>如果请求成功，我们看到类似于 <code class="literal">index</code> 请求的响应：</p>
<div class="pre_wrapper lang-js">
<pre class="programlisting prettyprint lang-js">{
   "_index" :   "website",
   "_id" :      "1",
   "_type" :    "blog",
   "_version" : 3
}</pre>
</div>
<p>检索文档显示了更新后的 <code class="literal">_source</code> 字段：</p>
<div class="pre_wrapper lang-sense">
<pre class="programlisting prettyprint lang-sense">{
   "_index":    "website",
   "_type":     "blog",
   "_id":       "1",
   "_version":  3,
   "found":     true,
   "_source": {
      "title":  "My first blog entry",
      "text":   "Starting to get the hang of this...",
      "tags": [ "testing" ], <a id="CO12-1"></a><i class="conum" data-value="1"></i>
      "views":  0 <a id="CO12-2"></a><i class="conum" data-value="1"></i>
   }
}</pre>
</div>
<div class="sense_widget" data-snippet="snippets/030_Data/45_Partial_update.json"></div>
<div class="calloutlist">
<table border="0" summary="Callout list">
<tr>
<td align="left" valign="top" width="5%">
<p><a href="#CO12-1"><i class="conum" data-value="1"></i></a><a href="#CO12-2"></a></p>
</td>
<td align="left" valign="top">
<p>新的字段已被添加到 <code class="literal">_source</code> 中。</p>
</td>
</tr>
</table>
</div>
<div class="section">
<div class="titlepage"><div><div>
<h3 class="title">
<a id="_Using_Scripts_to_Make_Partial_Updates"></a>使用脚本部分更新文档<a class="edit_me edit_me_private" rel="nofollow" title="Editing on GitHub is available to Elastic" href="https://github.com/elasticsearch-cn/elasticsearch-definitive-guide/edit/cn/030_Data/45_Partial_update.asciidoc">edit</a>
</h3>
</div></div></div>
<p>脚本可以在 <code class="literal">update</code> API中用来改变 <code class="literal">_source</code> 的字段内容，
它在更新脚本中称为 <code class="literal">ctx._source</code> 。 例如，我们可以使用脚本来增加博客文章中 <code class="literal">views</code> 的数量：</p>
<div class="pre_wrapper lang-sense">
<pre class="programlisting prettyprint lang-sense">POST /website/blog/1/_update
{
   "script" : "ctx._source.views+=1"
}</pre>
</div>
<div class="sense_widget" data-snippet="snippets/030_Data/45_Partial_update.json"></div>
<div class="sidebar">
<div class="titlepage"><div><div>
<p class="title"><strong>用 Groovy 脚本编程</strong></p>
</div></div></div>
<p>对于那些 API 不能满足需求的情况，Elasticsearch 允许你使用脚本编写自定义的逻辑。
许多API都支持脚本的使用，包括搜索、排序、聚合和文档更新。
脚本可以作为请求的一部分被传递，从特殊的 .scripts 索引中检索，或者从磁盘加载脚本。</p>
<p>默认的脚本语言 是 <a href="http://groovy.codehaus.org/" class="ulink" target="_top">Groovy</a>，一种快速表达的脚本语言，在语法上与 JavaScript 类似。
它在 Elasticsearch V1.3.0 版本首次引入并运行在 <em>沙盒</em> 中，然而 Groovy 脚本引擎存在漏洞，
允许攻击者通过构建 Groovy 脚本，在 Elasticsearch Java VM 运行时脱离沙盒并执行 shell 命令。</p>
<p>因此，在版本 v1.3.8 、 1.4.3 和 V1.5.0 及更高的版本中，它已经被默认禁用。
此外，您可以通过设置集群中的所有节点的 <code class="literal">config/elasticsearch.yml</code> 文件来禁用动态 Groovy 脚本：</p>
<div class="pre_wrapper lang-yaml">
<pre class="programlisting prettyprint lang-yaml">script.groovy.sandbox.enabled: false</pre>
</div>
<p>这将关闭 Groovy 沙盒，从而防止动态 Groovy 脚本作为请求的一部分被接受，
或者从特殊的 <code class="literal">.scripts</code> 索引中被检索。当然，你仍然可以使用存储在每个节点的 <code class="literal">config/scripts/</code> 目录下的 Groovy 脚本。</p>
<p>如果你的架构和安全性不需要担心漏洞攻击，例如你的 Elasticsearch 终端仅暴露和提供给可信赖的应用，
当它是你的应用需要的特性时，你可以选择重新启用动态脚本。</p>
<p>你可以在 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/5.6/modules-scripting.html" class="ulink" target="_top">scripting reference documentation</a> 获取更多关于脚本的资料。</p>
</div>
<p>我们也可以通过使用脚本给 <code class="literal">tags</code> 数组添加一个新的标签。在这个例子中，我们指定新的标签作为参数，而不是硬编码到脚本内部。
这使得 Elasticsearch 可以重用这个脚本，而不是每次我们想添加标签时都要对新脚本重新编译：</p>
<div class="pre_wrapper lang-sense">
<pre class="programlisting prettyprint lang-sense">POST /website/blog/1/_update
{
   "script" : "ctx._source.tags+=new_tag",
   "params" : {
      "new_tag" : "search"
   }
}</pre>
</div>
<div class="sense_widget" data-snippet="snippets/030_Data/45_Partial_update.json"></div>
<p>获取文档并显示最后两次请求的效果：</p>
<div class="pre_wrapper lang-js">
<pre class="programlisting prettyprint lang-js">{
   "_index":    "website",
   "_type":     "blog",
   "_id":       "1",
   "_version":  5,
   "found":     true,
   "_source": {
      "title":  "My first blog entry",
      "text":   "Starting to get the hang of this...",
      "tags":  ["testing", "search"], <a id="CO13-1"></a><i class="conum" data-value="1"></i>
      "views":  1 <a id="CO13-2"></a><i class="conum" data-value="2"></i>
   }
}</pre>
</div>
<div class="calloutlist">
<table border="0" summary="Callout list">
<tr>
<td align="left" valign="top" width="5%">
<p><a href="#CO13-1"><i class="conum" data-value="1"></i></a></p>
</td>
<td align="left" valign="top">
<p><code class="literal">search</code> 标签已追加到 <code class="literal">tags</code> 数组中。</p>
</td>
</tr>
<tr>
<td align="left" valign="top" width="5%">
<p><a href="#CO13-2"><i class="conum" data-value="2"></i></a></p>
</td>
<td align="left" valign="top">
<p><code class="literal">views</code> 字段已递增。</p>
</td>
</tr>
</table>
</div>
<p>我们甚至可以选择通过设置 <code class="literal">ctx.op</code> 为 <code class="literal">delete</code> 来删除基于其内容的文档：</p>
<div class="pre_wrapper lang-sense">
<pre class="programlisting prettyprint lang-sense">POST /website/blog/1/_update
{
   "script" : "ctx.op = ctx._source.views == count ? 'delete' : 'none'",
    "params" : {
        "count": 1
    }
}</pre>
</div>
<div class="sense_widget" data-snippet="snippets/030_Data/45_Partial_update.json"></div>
</div>

<div class="section">
<div class="titlepage"><div><div>
<h3 class="title">
<a id="_Updating_a_Document_That_May_Not_Yet_Exist"></a>更新的文档可能尚不存在<a class="edit_me edit_me_private" rel="nofollow" title="Editing on GitHub is available to Elastic" href="https://github.com/elasticsearch-cn/elasticsearch-definitive-guide/edit/cn/030_Data/45_Partial_update.asciidoc">edit</a>
</h3>
</div></div></div>
<p>假设我们需要在 Elasticsearch 中存储一个页面访问量计数器。
每当有用户浏览网页，我们对该页面的计数器进行累加。但是，如果它是一个新网页，我们不能确定计数器已经存在。
如果我们尝试更新一个不存在的文档，那么更新操作将会失败。</p>
<p>在这样的情况下，我们可以使用 <code class="literal">upsert</code> 参数，指定如果文档不存在就应该先创建它：</p>
<div class="pre_wrapper lang-sense">
<pre class="programlisting prettyprint lang-sense">POST /website/pageviews/1/_update
{
   "script" : "ctx._source.views+=1",
   "upsert": {
       "views": 1
   }
}</pre>
</div>
<div class="sense_widget" data-snippet="snippets/030_Data/45_Upsert.json"></div>
<p>我们第一次运行这个请求时， <code class="literal">upsert</code> 值作为新文档被索引，初始化 <code class="literal">views</code> 字段为 <code class="literal">1</code> 。
在后续的运行中，由于文档已经存在， <code class="literal">script</code> 更新操作将替代 <code class="literal">upsert</code> 进行应用，对 <code class="literal">views</code> 计数器进行累加。</p>
</div>

<div class="section">
<div class="titlepage"><div><div>
<h3 class="title">
<a id="_Updates_and_Conflicts"></a>更新和冲突<a class="edit_me edit_me_private" rel="nofollow" title="Editing on GitHub is available to Elastic" href="https://github.com/elasticsearch-cn/elasticsearch-definitive-guide/edit/cn/030_Data/45_Partial_update.asciidoc">edit</a>
</h3>
</div></div></div>
<p>在本节的介绍中，我们说明 <em>检索</em> 和 <em>重建索引</em> 步骤的间隔越小，变更冲突的机会越小。
但是它并不能完全消除冲突的可能性。 还是有可能在 <code class="literal">update</code> 设法重新索引之前，来自另一进程的请求修改了文档。</p>
<p>为了避免数据丢失， <code class="literal">update</code> API 在 <em>检索</em> 步骤时检索得到文档当前的 <code class="literal">_version</code> 号，并传递版本号到 <em>重建索引</em> 步骤的 <code class="literal">index</code> 请求。
如果另一个进程修改了处于检索和重新索引步骤之间的文档，那么 <code class="literal">_version</code> 号将不匹配，更新请求将会失败。</p>
<p>对于部分更新的很多使用场景，文档已经被改变也没有关系。
例如，如果两个进程都对页面访问量计数器进行递增操作，它们发生的先后顺序其实不太重要；
如果冲突发生了，我们唯一需要做的就是尝试再次更新。</p>
<p>这可以通过设置参数 <code class="literal">retry_on_conflict</code> 来自动完成，
这个参数规定了失败之前 <code class="literal">update</code> 应该重试的次数，它的默认值为 <code class="literal">0</code> 。</p>
<div class="pre_wrapper lang-sense">
<pre class="programlisting prettyprint lang-sense">POST /website/pageviews/1/_update?retry_on_conflict=5 <a id="CO14-1"></a><i class="conum" data-value="1"></i>
{
   "script" : "ctx._source.views+=1",
   "upsert": {
       "views": 0
   }
}</pre>
</div>
<div class="sense_widget" data-snippet="snippets/030_Data/45_Upsert.json"></div>
<div class="calloutlist">
<table border="0" summary="Callout list">
<tr>
<td align="left" valign="top" width="5%">
<p><a href="#CO14-1"><i class="conum" data-value="1"></i></a></p>
</td>
<td align="left" valign="top">
<p>失败之前重试该更新5次。</p>
</td>
</tr>
</table>
</div>
<p>在增量操作无关顺序的场景，例如递增计数器等这个方法十分有效，但是在其他情况下变更的顺序 <em>是</em> 非常重要的。
类似 <a class="xref" href="index-doc.html" title="索引文档"><code class="literal">index</code> API</a> ， <code class="literal">update</code> API 默认采用 <em>最终写入生效</em> 的方案，但它也接受一个 <code class="literal">version</code> 参数来允许你使用 <a class="xref" href="optimistic-concurrency-control.html" title="乐观并发控制">optimistic concurrency control</a> 指定想要更新文档的版本。</p>
</div>

</div>
<div class="navfooter">
<span class="prev">
<a href="optimistic-concurrency-control.html">« 乐观并发控制</a>
</span>
<span class="next">
<a href="_Retrieving_Multiple_Documents.html">取回多个文档 »</a>
</span>
</div>
</div>

                  <!-- end body -->
                        </div>
                        <div class="col-xs-12 col-sm-4 col-md-4" id="right_col">
                        
                        </div>
                    </div>
                </div>
            </section>
        </div>
    </section>
</div>
<script src="../static/cn.js"></script>
</body>
</html>